// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package view provides a type presenting a read-only view of bytes.
package view

import (
	"bytes"
	"errors"
	"io"
	"strings"
)

// Bytes is a read-only view of bytes from either a string
// or []byte.
//
// Programs using Bytes should typically store and pass them as
// values, not pointers. That is, Bytes variables and struct fields
// should be of type view.Bytes, not *view.Bytes. A Bytes value can be
// used by multiple goroutines simultaneously.
type Bytes struct {
	b []byte // used if non-nil, else s is used
	s string
}

// OfString returns a Bytes view wrapping the provided string.
func OfString(s string) Bytes {
	return Bytes{s: s}
}

// OfBytes returns a Bytes view wrapping the provided byte slice.
//
// No copy is made of the slice; any mutations will be visible to
// readers of the the returned Bytes view.
func OfBytes(b []byte) Bytes {
	return Bytes{b: b}
}

// Len returns the number of bytes in b.
func (b Bytes) Len() int {
	if b.b != nil {
		return len(b.b)
	}
	return len(b.s)
}

// At returns the byte at index i.
func (b Bytes) At(i int) byte {
	if b.b != nil {
		return b.b[i]
	}
	return b.s[i]
}

// Slice slices the Bytes view between the provided from and to indices.
func (b Bytes) Slice(from, to int) Bytes {
	if b.b != nil {
		return Bytes{b: b.b[from:to]}
	}
	return Bytes{s: b.s[from:to]}
}

// SliceFrom slices the Bytes view from the provided index until the end.
func (b Bytes) SliceFrom(from int) Bytes {
	if b.b != nil {
		return Bytes{b: b.b[from:]}
	}
	return Bytes{s: b.s[from:]}
}

// Copy copies b into dest and returns the number of bytes copied.
func (b Bytes) Copy(dest []byte) int {
	if b.b != nil {
		return copy(dest, b.b)
	}
	return copy(dest, b.s)
}

// String returns the byte view as a string.
//
// If the underlying representation is already a string, no copy is
// made.
func (b Bytes) String() string {
	if b.b != nil {
		return string(b.b)
	}
	return b.s
}

// Equal returns whether the bytes in b are the same as the bytes in
// b2.
func (b Bytes) Equal(b2 Bytes) bool {
	if b2.b == nil {
		return b.EqualString(b2.s)
	}
	return b.EqualBytes(b2.b)
}

// EqualString returns whether the bytes in b are the same as the bytes
// in s.
func (b Bytes) EqualString(s string) bool {
	if b.b == nil {
		return b.s == s
	}
	l := b.Len()
	if len(s) != l {
		return false
	}
	for i, bi := range b.b {
		if bi != s[i] {
			return false
		}
	}
	return true
}

// EqualBytes returns whether the bytes in b are the same as the bytes
// in b2.
func (b Bytes) EqualBytes(b2 []byte) bool {
	if b.b != nil {
		return bytes.Equal(b.b, b2)
	}
	l := b.Len()
	if len(b2) != l {
		return false
	}
	for i, bi := range b2 {
		if bi != b.s[i] {
			return false
		}
	}
	return true
}

// Reader returns a reader for the bytes in b.
func (b Bytes) Reader() io.ReadSeeker {
	if b.b != nil {
		return bytes.NewReader(b.b)
	}
	return strings.NewReader(b.s)
}

// ReadAt implements io.ReaderAt on the bytes in b.
func (b Bytes) ReadAt(p []byte, off int64) (n int, err error) {
	if off < 0 {
		return 0, errors.New("view: invalid offset")
	}
	if off >= int64(b.Len()) {
		return 0, io.EOF
	}
	n = b.SliceFrom(int(off)).Copy(p)
	if n < len(p) {
		err = io.EOF
	}
	return
}
